1
|
|
|
/**
|
2
|
|
|
* Flot plugin that provides spline interpolation for line graphs
|
3
|
|
|
* author: Alex Bardas < [email protected] >
|
4
|
|
|
* modified by: Avi Kohn https://github.com/AMKohn
|
5
|
|
|
* based on the spline interpolation described at:
|
6
|
|
|
* http://scaledinnovation.com/analytics/splines/aboutSplines.html
|
7
|
|
|
*
|
8
|
|
|
* Example usage: (add in plot options series object)
|
9
|
|
|
* for linespline:
|
10
|
|
|
* series: {
|
11
|
|
|
* ...
|
12
|
|
|
* lines: {
|
13
|
|
|
* show: false
|
14
|
|
|
* },
|
15
|
|
|
* splines: {
|
16
|
|
|
* show: true,
|
17
|
|
|
* tension: x, (float between 0 and 1, defaults to 0.5),
|
18
|
|
|
* lineWidth: y (number, defaults to 2),
|
19
|
|
|
* fill: z (float between 0 .. 1 or false, as in flot documentation)
|
20
|
|
|
* },
|
21
|
|
|
* ...
|
22
|
|
|
* }
|
23
|
|
|
* areaspline:
|
24
|
|
|
* series: {
|
25
|
|
|
* ...
|
26
|
|
|
* lines: {
|
27
|
|
|
* show: true,
|
28
|
|
|
* lineWidth: 0, (line drawing will not execute)
|
29
|
|
|
* fill: x, (float between 0 .. 1, as in flot documentation)
|
30
|
|
|
* ...
|
31
|
|
|
* },
|
32
|
|
|
* splines: {
|
33
|
|
|
* show: true,
|
34
|
|
|
* tension: 0.5 (float between 0 and 1)
|
35
|
|
|
* },
|
36
|
|
|
* ...
|
37
|
|
|
* }
|
38
|
|
|
*
|
39
|
|
|
*/
|
40
|
|
|
|
41
|
|
|
(function($) {
|
42
|
|
|
'use strict'
|
43
|
|
|
|
44
|
|
|
/**
|
45
|
|
|
* @param {Number} x0, y0, x1, y1: coordinates of the end (knot) points of the segment
|
46
|
|
|
* @param {Number} x2, y2: the next knot (not connected, but needed to calculate p2)
|
47
|
|
|
* @param {Number} tension: control how far the control points spread
|
48
|
|
|
* @return {Array}: p1 -> control point, from x1 back toward x0
|
49
|
|
|
* p2 -> the next control point, returned to become the next segment's p1
|
50
|
|
|
*
|
51
|
|
|
* @api private
|
52
|
|
|
*/
|
53
|
|
|
function getControlPoints(x0, y0, x1, y1, x2, y2, tension) {
|
54
|
|
|
|
55
|
|
|
var pow = Math.pow,
|
56
|
|
|
sqrt = Math.sqrt,
|
57
|
|
|
d01, d12, fa, fb, p1x, p1y, p2x, p2y;
|
58
|
|
|
|
59
|
|
|
// Scaling factors: distances from this knot to the previous and following knots.
|
60
|
|
|
d01 = sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2));
|
61
|
|
|
d12 = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
|
62
|
|
|
|
63
|
|
|
fa = tension * d01 / (d01 + d12);
|
64
|
|
|
fb = tension - fa;
|
65
|
|
|
|
66
|
|
|
p1x = x1 + fa * (x0 - x2);
|
67
|
|
|
p1y = y1 + fa * (y0 - y2);
|
68
|
|
|
|
69
|
|
|
p2x = x1 - fb * (x0 - x2);
|
70
|
|
|
p2y = y1 - fb * (y0 - y2);
|
71
|
|
|
|
72
|
|
|
return [p1x, p1y, p2x, p2y];
|
73
|
|
|
}
|
74
|
|
|
|
75
|
|
|
var line = [];
|
76
|
|
|
|
77
|
|
|
function drawLine(points, ctx, height, fill, seriesColor) {
|
78
|
|
|
var c = $.color.parse(seriesColor);
|
79
|
|
|
|
80
|
|
|
c.a = typeof fill == "number" ? fill : .3;
|
81
|
|
|
c.normalize();
|
82
|
|
|
c = c.toString();
|
83
|
|
|
|
84
|
|
|
ctx.beginPath();
|
85
|
|
|
ctx.moveTo(points[0][0], points[0][1]);
|
86
|
|
|
|
87
|
|
|
var plength = points.length;
|
88
|
|
|
|
89
|
|
|
for (var i = 0; i < plength; i++) {
|
90
|
|
|
ctx[points[i][3]].apply(ctx, points[i][2]);
|
91
|
|
|
}
|
92
|
|
|
|
93
|
|
|
ctx.stroke();
|
94
|
|
|
|
95
|
|
|
ctx.lineWidth = 0;
|
96
|
|
|
ctx.lineTo(points[plength - 1][0], height);
|
97
|
|
|
ctx.lineTo(points[0][0], height);
|
98
|
|
|
|
99
|
|
|
ctx.closePath();
|
100
|
|
|
|
101
|
|
|
if (fill !== false) {
|
102
|
|
|
ctx.fillStyle = c;
|
103
|
|
|
ctx.fill();
|
104
|
|
|
}
|
105
|
|
|
}
|
106
|
|
|
|
107
|
|
|
/**
|
108
|
|
|
* @param {Object} ctx: canvas context
|
109
|
|
|
* @param {String} type: accepted strings: 'bezier' or 'quadratic' (defaults to quadratic)
|
110
|
|
|
* @param {Array} points: 2 points for which to draw the interpolation
|
111
|
|
|
* @param {Array} cpoints: control points for those segment points
|
112
|
|
|
*
|
113
|
|
|
* @api private
|
114
|
|
|
*/
|
115
|
|
|
function queue(ctx, type, points, cpoints) {
|
116
|
|
|
if (type === void 0 || (type !== 'bezier' && type !== 'quadratic')) {
|
|
|
|
|
117
|
|
|
type = 'quadratic';
|
118
|
|
|
}
|
119
|
|
|
type = type + 'CurveTo';
|
120
|
|
|
|
121
|
|
|
if (line.length == 0) line.push([points[0], points[1], cpoints.concat(points.slice(2)), type]);
|
|
|
|
|
122
|
|
|
else if (type == "quadraticCurveTo" && points.length == 2) {
|
123
|
|
|
cpoints = cpoints.slice(0, 2).concat(points);
|
124
|
|
|
|
125
|
|
|
line.push([points[0], points[1], cpoints, type]);
|
126
|
|
|
}
|
127
|
|
|
else line.push([points[2], points[3], cpoints.concat(points.slice(2)), type]);
|
|
|
|
|
128
|
|
|
}
|
129
|
|
|
|
130
|
|
|
/**
|
131
|
|
|
* @param {Object} plot
|
132
|
|
|
* @param {Object} ctx: canvas context
|
133
|
|
|
* @param {Object} series
|
134
|
|
|
*
|
135
|
|
|
* @api private
|
136
|
|
|
*/
|
137
|
|
|
|
138
|
|
|
function drawSpline(plot, ctx, series) {
|
139
|
|
|
// Not interested if spline is not requested
|
140
|
|
|
if (series.splines.show !== true) {
|
141
|
|
|
return;
|
142
|
|
|
}
|
143
|
|
|
|
144
|
|
|
var cp = [],
|
145
|
|
|
// array of control points
|
146
|
|
|
tension = series.splines.tension || 0.5,
|
147
|
|
|
idx, x, y, points = series.datapoints.points,
|
148
|
|
|
ps = series.datapoints.pointsize,
|
149
|
|
|
plotOffset = plot.getPlotOffset(),
|
150
|
|
|
len = points.length,
|
151
|
|
|
pts = [];
|
152
|
|
|
|
153
|
|
|
line = [];
|
154
|
|
|
|
155
|
|
|
// Cannot display a linespline/areaspline if there are less than 3 points
|
156
|
|
|
if (len / ps < 4) {
|
157
|
|
|
$.extend(series.lines, series.splines);
|
158
|
|
|
return;
|
159
|
|
|
}
|
160
|
|
|
|
161
|
|
|
for (idx = 0; idx < len; idx += ps) {
|
162
|
|
|
x = points[idx];
|
163
|
|
|
y = points[idx + 1];
|
164
|
|
|
if (x == null || x < series.xaxis.min || x > series.xaxis.max || y < series.yaxis.min || y > series.yaxis.max) {
|
165
|
|
|
continue;
|
166
|
|
|
}
|
167
|
|
|
|
168
|
|
|
pts.push(series.xaxis.p2c(x) + plotOffset.left, series.yaxis.p2c(y) + plotOffset.top);
|
169
|
|
|
}
|
170
|
|
|
|
171
|
|
|
len = pts.length;
|
172
|
|
|
|
173
|
|
|
// Draw an open curve, not connected at the ends
|
174
|
|
|
for (idx = 0; idx < len - 2; idx += 2) {
|
175
|
|
|
cp = cp.concat(getControlPoints.apply(this, pts.slice(idx, idx + 6).concat([tension])));
|
176
|
|
|
}
|
177
|
|
|
|
178
|
|
|
ctx.save();
|
179
|
|
|
ctx.strokeStyle = series.color;
|
180
|
|
|
ctx.lineWidth = series.splines.lineWidth;
|
181
|
|
|
|
182
|
|
|
queue(ctx, 'quadratic', pts.slice(0, 4), cp.slice(0, 2));
|
183
|
|
|
|
184
|
|
|
for (idx = 2; idx < len - 3; idx += 2) {
|
185
|
|
|
queue(ctx, 'bezier', pts.slice(idx, idx + 4), cp.slice(2 * idx - 2, 2 * idx + 2));
|
186
|
|
|
}
|
187
|
|
|
|
188
|
|
|
queue(ctx, 'quadratic', pts.slice(len - 2, len), [cp[2 * len - 10], cp[2 * len - 9], pts[len - 4], pts[len - 3]]);
|
189
|
|
|
|
190
|
|
|
drawLine(line, ctx, plot.height() + 10, series.splines.fill, series.color);
|
191
|
|
|
|
192
|
|
|
ctx.restore();
|
193
|
|
|
}
|
194
|
|
|
|
195
|
|
|
$.plot.plugins.push({
|
196
|
|
|
init: function(plot) {
|
197
|
|
|
plot.hooks.drawSeries.push(drawSpline);
|
198
|
|
|
},
|
199
|
|
|
options: {
|
200
|
|
|
series: {
|
201
|
|
|
splines: {
|
202
|
|
|
show: false,
|
203
|
|
|
lineWidth: 2,
|
204
|
|
|
tension: 0.5,
|
205
|
|
|
fill: false
|
206
|
|
|
}
|
207
|
|
|
}
|
208
|
|
|
},
|
209
|
|
|
name: 'spline',
|
210
|
|
|
version: '0.8.2'
|
211
|
|
|
});
|
212
|
|
|
})(jQuery);
|
213
|
|
|
|